package cbfg.http

import android.webkit.URLUtil
import cbfg.http.annotation.*
import cbfg.http.constant.PostType
import cbfg.http.constant.RequestType
import cbfg.http.subscribe.Observable
import cbfg.http.util.Logger
import com.google.gson.reflect.TypeToken
import okhttp3.Request
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.*
import kotlin.collections.HashMap

/**
 * 请求方法
 */
class ServiceMethod<T>(method: Method) {
    //请求头
    private var headers: Headers? = null

    //参数注解
    private lateinit var parameterAnnotations: Array<Array<Annotation>>

    //地址
    private lateinit var requestUrl: String

    //获取标签
    private var hostTag: String? = null
    private var httpTag: String? = null

    private var postType = PostType.JSON

    //返回类型
    private val returnType: Type =
        (method.genericReturnType as ParameterizedType).actualTypeArguments[0]

    private lateinit var requestType: RequestType
    private var gotField = false
    private var gotPart = false
    private var gotBody = false

    init {
        parseAnnotation(method)
    }

    /**
     * 解析该方法的与类的注解
     * @param method
     */
    private fun parseAnnotation(method: Method) {
        //获取标签
        val mHostTag = method.declaringClass.getAnnotation(HostTag::class.java)?.value
        hostTag = mHostTag
        httpTag = method.getAnnotation(HttpTag::class.java)?.value

        //获取方法/类请求头
        headers = method.getAnnotation(Headers::class.java) ?: method.declaringClass.getAnnotation(
            Headers::class.java
        )

        //获取参数注解/参数请求头
        parameterAnnotations = method.parameterAnnotations

        //获取请求类型
        var mRequestType: RequestType? = null

        for (annotation in method.annotations) {
            when (annotation) {
                is GET -> {
                    require(mRequestType == null) { "Only one HTTP method is allowed. Found: $requestType and GET." }
                    mRequestType = RequestType.GET
                    this.requestUrl = assembleUrl(mHostTag, annotation.value)
                }
                is POST -> {
                    require(mRequestType == null) { "Only one HTTP method is allowed. Found: $requestType and POST." }
                    mRequestType = RequestType.POST
                    this.requestUrl = assembleUrl(mHostTag, annotation.value)
                }
                is FormUrlEncoded -> {
                    require(postType != PostType.MULTI_PART) { "Only one encoding annotation is allowed." }
                    postType = PostType.FORM
                }
                is Multipart -> {
                    require(postType != PostType.FORM) { "Only one encoding annotation is allowed." }
                    postType = PostType.MULTI_PART
                }
            }
        }

        requestType = mRequestType
            ?: throw IllegalArgumentException("HTTP method annotation is required (e.g., @GET, @POST).")
    }

    /**
     * 组装 url
     * 判断是否配置了完整的 url，如果是的话不用拼接 base url
     */
    private fun assembleUrl(hostTag: String?, mainUrl: String): String {
        val baseUrl = if (hostTag == null) {
            HttpManager.getBaseUrl() ?: ""
        } else {
            HttpManager.getHostUrl(hostTag) ?: ""
        }
        return if (URLUtil.isHttpUrl(mainUrl) || URLUtil.isHttpsUrl(mainUrl)) mainUrl
        else baseUrl + mainUrl
    }

    operator fun invoke(args: Array<Any?>?): Observable<T>? {
        val request = HttpManager.converterFactory.convertRequest(
            hostTag,
            httpTag,
            createRequest(requestType, args)
        )
        Logger.i("Headers : ${request.headers()}")
        return OkHttpCall<T>(hostTag, httpTag, returnType).prepare(request)
    }

    private fun createRequest(requestType: RequestType, args: Array<Any?>?): Request {
        val url = getUrl(args)
        Logger.i("$requestType $url")
        val requestBuilder = Request.Builder().url(url)
        //添加 Headers
        val headers = getHeaders(args)
        if (headers.isNotEmpty()) {
            val headersBuilder = okhttp3.Headers.Builder()
            headers.forEach { headersBuilder.add(it) }
            requestBuilder.headers(headersBuilder.build())
        }
        if (requestType == RequestType.GET) {
            args?.run { getCheckedRequestParams(this) }
            requestBuilder.get()
        } else {
            val params = getCheckedRequestParams(args!!)
            Logger.i("Params : $params")
            requestBuilder.post(
                HttpManager.converterFactory.convertRequestBody(
                    hostTag,
                    httpTag,
                    postType,
                    params
                )
            )
        }
        return requestBuilder.build()
    }

    private fun getHeaders(args: Array<Any?>?): List<String> {
        val mHeaders = ArrayList<String>()
        this.headers?.run { mHeaders.addAll(this.value.toList()) }
        args ?: return mHeaders
        for (i in parameterAnnotations.indices) {
            for (annotation in parameterAnnotations[i]) {
                if (annotation is Header) {
                    val arg = args[i]
                    if ((arg is String) && arg.isNotBlank()) {
                        mHeaders.add("${annotation.value}: $arg")
                    }
                }
            }
        }
        return mHeaders
    }

    private fun getUrl(args: Array<Any?>?): String {
        args ?: return requestUrl
        var mHost = requestUrl
        for (i in parameterAnnotations.indices) {
            for (annotation in parameterAnnotations[i]) {
                if (annotation is Path) {
                    val arg = args[i]
                    if ((arg is String) && arg.isNotBlank()) {
                        mHost = mHost.replace("{${annotation.value}}", arg)
                    }
                }
            }
        }
        return mHost
    }

    /**
     * 获取请求参数列表
     */
    private fun getCheckedRequestParams(args: Array<Any?>): Map<String, Any> {
        gotField = false
        gotBody = false
        gotPart = false

        val params: MutableMap<String, Any> = HashMap()
        for (i in parameterAnnotations.indices) {
            for (annotation in parameterAnnotations[i]) {
                val arg = args[i] ?: continue
                when (annotation) {
                    is Field -> {
                        require(postType != PostType.MULTI_PART) { "@Field parameters cannot be used with multipart encoding." }
                        require(arg is String) { "@Field parameters' value must be String." }
                        if (arg.isNotEmpty()) {
                            params[annotation.value] = arg
                            gotField = true
                        }
                    }
                    is Body -> {
                        require(postType != PostType.MULTI_PART) { "@Body parameters cannot be used with multi-part encoding." }
                        require(!gotBody) { "Multiple @Body method annotations found." }
                        gotBody = true

                        if (arg is Map<*, *>) {
                            params.putAll(arg as Map<String, Any>)
                        } else {
                            val json = HttpManager.gson.toJson(arg)
                            val map: Map<String, Any> = HttpManager.gson.fromJson(
                                json,
                                object : TypeToken<Map<String, Any>>() {}.type
                            )
                            params.putAll(map)
                        }
                    }
                    is Part -> {
                        require(postType == PostType.MULTI_PART) { "@Part parameters can only be used with multipart encoding." }
                        gotPart = true
                        params[annotation.value] = arg
                    }
                }
            }
        }

        /**
         * 检查参数是否合法
         */
        if (requestType == RequestType.GET) {
            if (gotField || gotBody || postType != PostType.JSON) {
                throw IllegalArgumentException("@Field|@Body|@FormUrlEncoded|@Multipart can only be specified on HTTP methods with request body (e.g., @POST).")
            }
        } else if (postType == PostType.MULTI_PART) {
            if (!gotPart) {
                throw IllegalArgumentException("Multipart method must contain at least one @Part.")
            }
        } else if (gotField) {
            if (gotBody) {
                throw IllegalArgumentException("@Field and @Body cannot exist at the same time.")
            }
        } else if (!gotBody) {
            throw IllegalArgumentException("Form|Json post method must contain at least one @Field or @Body.")
        }

        return params
    }
}